﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.ComponentModel;

namespace System.Windows.Input
{
    public class AsyncCommand<T, TResult> : ICommand, INotifyPropertyChanged
    {
        internal AsyncFunc<T, TResult> _func;

        public AsyncCommand(Func<T, TResult> func)
            : this((arg, isCancelled, reportProgress) => func(arg))
        {
        }

        public AsyncCommand(Func<T, Func<bool>, TResult> func)
            : this((arg, isCancelled, reportProgress) => func(arg, isCancelled))
        {
        }

        public AsyncCommand(Func<T, Action<int>, TResult> func)
            : this((arg, isCancelled, reportProgress) => func(arg, reportProgress))
        {
        }

        public AsyncCommand(Func<T, Func<bool>, Action<int>, TResult> func)
        {
            if (func == null) throw new ArgumentNullException("func");
            _func = new AsyncFunc<T, TResult>(func);
            _func.Completed += new AsyncFuncCompletedEventHandler<TResult>(_func_Completed);
            _func.ProgressChanged += new ProgressChangedEventHandler(_func_ProgressChanged);
        }

        public object Sender
        {
            get { return _func.Sender; }
            set { _func.Sender = value; }
        }

        void _func_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Progress = e.ProgressPercentage;
        }

        void _func_Completed(object sender, AsyncFuncCompletedEventArgs<TResult> e)
        {
            Progress = 0;
            RaiseCanExecuteChanged();
        }

        public bool CanExecute(object parameter)
        {
            return _func.IsBusy == false;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            T arg = (T)parameter;
            _func.InvokeAsync(arg);
            RaiseCanExecuteChanged();
        }

        private void RaiseCanExecuteChanged()
        {
            ReportPropertyChanged("IsBusy");
            EventHandler snapshot = CanExecuteChanged;
            if (snapshot != null)
            {
                snapshot.Invoke(this, EventArgs.Empty);
            }
        }

        public event AsyncFuncCompletedEventHandler<TResult> Completed
        {
            add { _func.Completed += value; }
            remove { _func.Completed -= value; }
        }



        public void Cancel()
        {
            _func.Cancel();
        }

        internal class CancelCommandInternal : ICommand
        {
            private AsyncCommand<T, TResult> _command;
            public CancelCommandInternal(AsyncCommand<T, TResult> command)
            {
                _command = command;
            }

            public bool CanExecute(object parameter)
            {
                return _command._func.IsBusy;
            }

            public event EventHandler CanExecuteChanged
            {
                add { _command.CanExecuteChanged += value; }
                remove { _command.CanExecuteChanged -= value; }
            }

            public void Execute(object parameter)
            {
                _command.Cancel();
            }
        }

        private CancelCommandInternal _cancel;
        public ICommand CancelCommand
        {
            get
            {
                if (_cancel == null)
                {
                    _cancel = new CancelCommandInternal(this);
                }
                return _cancel;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private int _progress = 0;
        public int Progress
        {
            get
            { return _progress; }
            private set
            {
                _progress = value;
                ReportPropertyChanged("Progress");
            }
        }

        public bool IsBusy { get { return _func.IsBusy; } }

        private void ReportPropertyChanged(string property)
        {
            PropertyChangedEventHandler snapshot = PropertyChanged;
            if (snapshot != null)
            {
                snapshot.Invoke(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}
